﻿namespace Vibstudio.NetConfigurator.Engine.Composition
{
	#region Namespaces

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Xml.Linq;

    using Vibstudio.NetConfigurator.Common;
    using Vibstudio.NetConfigurator.Exceptions;
    using Vibstudio.NetConfigurator.Extensions;
    using Vibstudio.NetConfigurator.Model;

	#endregion
	
    [Obfuscation(Exclude = true, ApplyToMembers = false)]
    internal class HttpComposer : IComposition
    {
        #region Fields

        private static Dictionary<HttpConfigurationIdentity, bool> compositionCache;

        private List<ConfigurationSetting> _exceptionsSettings;

        #endregion

        #region Public Methods and Operators

        public bool Compose(NetConfig netConfig)
        {
            if (compositionCache == null)
            {
                compositionCache =
                    new Dictionary<HttpConfigurationIdentity, bool>(new HttpConfigurationIdentityComparer());
            }

            _exceptionsSettings = ConfigurationHelper.GetSettings(netConfig.ConfigurationSettings, HttpSection.Exceptions);

            XDocument referenceFile = XDocument.Load(netConfig.ConfigurationFileReferencePath);
            XDocument configurationsFile = XDocument.Load(ConfigurationHelper.ConfigurationsFilePath);

            try
            {
                bool composed = ComposeHttp(referenceFile,
                                            configurationsFile,
                                            netConfig.ApplicationName,
                                            netConfig.ConfigurationID);
                if (composed)
                {
                    configurationsFile.Save(ConfigurationHelper.ConfigurationsFilePath);
                }

                return composed;
            }
            catch (AutocompositionAlreadyPerformedException)
            {
                return true;
            }
            catch (ApplicationNotReferentException)
            {
                return true;
            }
        }

        public void Reset()
        {
            compositionCache = null;
        }

        #endregion

        #region Methods

        private void ComposeByAttributes(XDocument referenceFile,
                                         XDocument configurationsFile,
                                         string appName,
                                         string appConfigName)
        {
            IEnumerable<XAttribute> httpReferenceAttributes =
                referenceFile
                    .Elements()
                    .Descendants()
                    .Attributes()
                    .Where(e => e.Value.StartsWith("http"));

            foreach (XAttribute httpAttribute in httpReferenceAttributes)
            {
                try
                {
                    PerformComposition(configurationsFile, appName, appConfigName, httpAttribute.Value);
                }
                catch (AutocompositionAlreadyPerformedException)
                {
                }
            }
        }

        private void ComposeByElements(XDocument referenceFile,
                                       XDocument configurationsFile,
                                       string appName,
                                       string appConfigName)
        {
            IEnumerable<XElement> httpReferenceElements =
                referenceFile
                    .Elements()
                    .Descendants()
                    .Where(e => e.Name == "value" && e.Value.StartsWith("http"));

            foreach (XElement httpElement in httpReferenceElements)
            {
                try
                {
                    PerformComposition(configurationsFile, appName, appConfigName, httpElement.Value);
                }
                catch (AutocompositionAlreadyPerformedException)
                {
                }
            }
        }

        private bool ComposeHttp(XDocument referenceFile,
                                 XDocument configurationsFile,
                                 string appName,
                                 string appConfigName)
        {
            try
            {
                ComposeByElements(referenceFile, configurationsFile, appName, appConfigName);

                ComposeByAttributes(referenceFile, configurationsFile, appName, appConfigName);

                return true;
            }
            catch (Exception e)
            {
                Trace.WriteLine(string.Format("Exception: {0}", e), "HttpAutoComposer.ComposeHttp");
                throw;
            }
        }

        private bool IsAlreadyComposed(HttpSection section, string resourceName, string hostName)
        {
            return compositionCache.ContainsKey(new HttpConfigurationIdentity(section, resourceName, hostName));
        }

        private void PerformComposition(XDocument targetFile,
                                        string appName,
                                        string appConfigName,
                                        string httpValue)
        {
            if (!httpValue.IsExceptionUrl(_exceptionsSettings.Select(e => e.Value))
                &&
                !httpValue.IsRootUrl()
                &&
                !httpValue.IsRelativeUrl())
            {
                string sectionName = Enum.GetName(typeof(HttpSection), HttpSection.Aspnet).ToLower();
                var section = HttpSection.Aspnet;
                string resourceName = httpValue.GetResourceName();
                string hostName = httpValue.GetHost();

                if (!httpValue.IsAspnetUrl())
                {
                    sectionName = Enum.GetName(typeof(HttpSection), HttpSection.Java).ToLower();
                    section = HttpSection.Java;
                }

                XElement httpElement =
                    targetFile
                        .Descendants("configuration")
                        .Elements("name")
                        .Where(e => (string)e.Attribute("id") == "http")
                        .Descendants(sectionName)
                        .FirstOrDefault();

                string applicationReference = string.Empty;

                if (httpElement != null)
                {
                    XAttribute apprefAttribute = httpElement.Attribute("appref");
                    if (apprefAttribute != null)
                    {
                        applicationReference = apprefAttribute.Value;
                    }
                }

                if (!string.IsNullOrEmpty(applicationReference))
                {
                    if (appName.ToLower() != applicationReference.ToLower())
                    {
                        throw new ApplicationNotReferentException(applicationReference);
                    }
                }

                var resourceNames = new List<string>();
                IEnumerable<string> httpResourceNames = targetFile
                    .Descendants("configuration")
                    .Elements("name")
                    .Where(e => (string)e.Attribute("id") == "http")
                    .Descendants(sectionName)
                    .Elements("add")
                    .Attributes("key")
                    .Select(key => key.Value);
                resourceNames.AddRange(httpResourceNames);

                if (!IsAlreadyComposed(section, resourceName, hostName))
                {
                    XElement targetElement;

                    bool resourcesContainsCurrent = false;
                    if (!string.IsNullOrEmpty(resourceName))
                    {
                        foreach (string s in resourceNames)
                        {
                            if (resourceName.Length >= s.Length)
                            {
                                if (s.ToLower() == resourceName.ToLower() ||
                                    resourceName.Substring(0, s.Length).ToLower() == s.ToLower())
                                {
                                    resourcesContainsCurrent = true;
                                }
                            }
                        }
                    }

                    if (resourcesContainsCurrent)
                    {
                        targetElement =
                            targetFile
                                .Descendants("configuration")
                                .Elements("name")
                                .Where(e => (string)e.Attribute("id") == "http")
                                .Descendants(sectionName)
                                .Elements("add")
                                .FirstOrDefault(a =>
                                    {
                                        XAttribute keyAttribute = a.Attribute("key");
                                        return keyAttribute != null
                                               &&
                                               keyAttribute.Value.ToLower() == resourceName.ToLower();
                                    });
                    }
                    else
                    {
                        targetElement =
                            targetFile
                                .Descendants("configuration")
                                .Elements("name")
                                .Where(e => (string)e.Attribute("id") == "http")
                                .Descendants(sectionName)
                                .Elements("add")
                                .FirstOrDefault(a => a.Attribute("key") == null);
                    }

                    if (targetElement != null)
                    {
                        XAttribute targetAttribute = targetElement.Attribute("value");
                        if (targetAttribute != null)
                        {
                            targetAttribute.SetValue(hostName);
                        }

                        SaveInCache(section, resourceName, hostName);
                    }
                }
                else
                {
                    throw new AutocompositionAlreadyPerformedException(appConfigName);
                }
            }
        }

        private void SaveInCache(HttpSection section, string resourceName, string hostName)
        {
            compositionCache.Add(new HttpConfigurationIdentity(section, resourceName, hostName), true);
        }

        #endregion
    }
}